﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Threading.Tasks;

namespace ElevatorDispatching
{
    /// <summary>
    /// 电梯
    /// </summary>
    public class Elevator : BindableBase, IEquatable<Elevator>, IComparable<Elevator>
    {
        private const int MOVING_TIMER_INTERVAL = 1000;
        private const int BOARDING_TIMER_INTERVAL = 2000;

        #region Properties and Fields

        private Timer _movingTimer;

        private Timer _boardingTimer;

        private IFloorGroup _floors;

        private Floor _currentFloor;

        /// <summary>
        /// 当前所在楼层
        /// </summary>
        public Floor CurrentFloor
        {
            get => _currentFloor;
            set => SetProperty(ref _currentFloor, value);
        }

        /// <summary>
        /// 最低可到达楼层
        /// </summary>
        public int LowestFloorId { get; }

        /// <summary>
        /// 最高可到达楼层
        /// </summary>
        public int HighestFloorId { get; }

        /// <summary>
        /// 电梯标识符
        /// </summary>
        public int Id { get; }

        private RunningStatus _runningStatus;

        /// <summary>
        /// 运行状态
        /// </summary>
        public RunningStatus RunningStatus
        {
            get => _runningStatus;
            private set
            {
                if (SetProperty(ref _runningStatus, value))
                {
                    RunningStatusChangedTime = DateTimeOffset.Now;
                }
            }
        }

        private DateTimeOffset _runningStatusChangedTime = DateTimeOffset.Now;

        /// <summary>
        /// 最后一次运行状态改变的时刻
        /// </summary>
        public DateTimeOffset RunningStatusChangedTime
        {
            get => _runningStatusChangedTime;
            private set => SetProperty(ref _runningStatusChangedTime, value);
        }

        /// <summary>
        /// 轿厢门的开关状态
        /// </summary>
        public DoorState DoorState { get; private set; }

        private Direction _runningDirection;

        /// <summary>
        /// 当前运行方向
        /// </summary>
        public Direction RunningDirection { get => _runningDirection; private set => SetProperty(ref _runningDirection, value); }

        /// <summary>
        /// 对轿厢内呼层按钮的响应状态
        /// </summary>
        public IDictionary<int, ButtonState> FloorButtonStates { get; } = new Dictionary<int, ButtonState>();

        /// <summary>
        /// 等待响应的呼梯信号集合
        /// </summary>
        public ISet<CallCageSignal> CallCageSignals { get; } = new HashSet<CallCageSignal>();

        #endregion

        #region Constructors

        public Elevator(int id, IFloorGroup floors, Floor currentFloor)
        {
            Id = id;

            _floors = floors;

            _movingTimer = new Timer(MOVING_TIMER_INTERVAL);
            _movingTimer.Elapsed += (sender, e) => CageMoving();

            _boardingTimer = new Timer(BOARDING_TIMER_INTERVAL);
            _boardingTimer.Elapsed += (sender, e) => CloseCageDoor();

            RunningStatus = RunningStatus.Stopped;
            RunningDirection = Direction.Unspecified;
            DoorState = false;
            LowestFloorId = _floors.Min(x => x.Id);
            HighestFloorId = _floors.Max(x => x.Id);
            CurrentFloor = currentFloor;

            // 初始化厅内呼层按钮
            foreach (var floor in _floors)
            {
                FloorButtonStates[floor.Id] = new ButtonState(false);
            }
        }

        #endregion

        /// <summary>
        /// 更新电梯的状态
        /// </summary>
        private void RefreshStatus()
        {
            if (DoorState.IsOpen) return;
            switch (RunningStatus)
            {
                case RunningStatus.Stopped:
                    if (RunningDirection == Direction.Unspecified)                  // 无方向停止
                    {
                        RunningDirection = DetermineRunningDirection();                                 // 根据呼层、呼梯信息决定运行方向

                        if (RunningDirection != Direction.Unspecified)                                  // 轿厢获得运行方向 =>
                        {
                            RunningStatus = RunningStatus.Running;                                                      // 向该方向运动
                            _movingTimer.Start();
                        }
                    }
                    else                                                            // 有方向停止
                    {
                        if (CallFloorSignalsExist(CurrentFloor.Id, RunningDirection) ||                 // 前方有呼层 或
                            CallCageSignalsExist(CurrentFloor.Id, RunningDirection))                    // 前方有呼梯 =>
                        {
                            RunningStatus = RunningStatus.Running;                                                      // 向当前方向移动
                            _movingTimer.Start();
                        }
                        else if (HasCallCageSignal(CurrentFloor.Id, RunningDirection.Reverse()))         // 当前层有反向呼梯
                        {
                            EraseAllCallFloorSignals();                                                                 // 重置轿厢内呼层信息
                            RunningDirection = RunningDirection.Reverse();                                              // 反转运行方向
                            OpenCageDoor();                                                                             // 轿厢门开启
                        }
                        else if (CallCageSignalsExist(CurrentFloor.Id, RunningDirection.Reverse()))      // 后方有呼梯 =>
                        {
                            EraseAllCallFloorSignals();                                                                 // 重置轿厢内呼层信息
                            RunningDirection = RunningDirection.Reverse();                                              // 翻转运行方向
                            RunningStatus = RunningStatus.Running;                                                      // 向翻转后的方向运行
                            _movingTimer.Start();
                        }
                        else                                                                            // 其他情况：前方无呼层，前后无呼梯 =>
                        {
                            EraseAllCallFloorSignals();                                                                 // 重置轿厢内呼层信息
                            RunningDirection = Direction.Unspecified;                                                   // 轿厢悬停在当前楼层
                        }
                    }
                    break;
                case RunningStatus.Running:                                         // 运行中
                    if (HasCallFloorSignal(CurrentFloor.Id) ||                                          // 当前层有呼层 或
                        HasCallCageSignal(CurrentFloor.Id, RunningDirection))                           // 当前层有同向呼梯 =>
                    {
                        RunningStatus = RunningStatus.Stopped;                                                          // 轿厢停止
                        OpenCageDoor();                                                                                 // 轿厢门开启
                    }
                    else if (CallFloorSignalsExist(CurrentFloor.Id, RunningDirection) ||                // 前方有呼层 或
                            CallCageSignalsExist(CurrentFloor.Id, RunningDirection))                    // 前方有呼梯 =>
                    {
                        _movingTimer.Start();                                                                           // 轿厢继续运行
                    }
                    else if (HasCallCageSignal(CurrentFloor.Id, RunningDirection.Reverse()))            // 当前层有反向呼梯 =>
                    {
                        RunningStatus = RunningStatus.Stopped;                                                          // 轿厢停止
                        EraseAllCallFloorSignals();                                                                     // 重置轿厢内呼层信息
                        RunningDirection = RunningDirection.Reverse();                                                  // 反转运行方向
                        OpenCageDoor();                                                                                 // 轿厢门开启
                    }
                    else                                                                                // 其他情况：当前层、前方均无呼层、呼梯信号
                    {
                        EraseAllCallFloorSignals();                                                                     // 重置轿厢内呼层信息
                        RunningDirection = Direction.Unspecified;                                                       // 轿厢悬停在当前楼层
                        RunningStatus = RunningStatus.Stopped;
                    }
                    break;
            }
        }

        /// <summary>
        /// 对内部呼层请求的响应
        /// </summary>
        /// <param name="floorId">请求的楼层</param>
        /// <returns></returns>
        public void CallFloor(int floorId)
        {
            RecordCallFloorSignal(floorId);
            if (RunningStatus == RunningStatus.Stopped)
            {
                if (IsCurrentFloor(floorId))
                {
                    OpenCageDoor();
                }
                else
                {
                    RefreshStatus();
                }
            }
        }

        /// <summary>
        /// 对外部呼梯信号的响应
        /// </summary>
        /// <param name="signal">厅外呼梯信号</param>
        /// <returns></returns>
        public void CallCage(CallCageSignal signal)
        {
            var floorId = signal.FloorId;
            var direction = signal.Direction;

            RecordCallCageSignal(signal);
            if (RunningStatus == RunningStatus.Stopped)
            {
                if (IsCurrentFloor(floorId))
                {
                    if (RunningDirection == Direction.Unspecified) RunningDirection = direction;
                    if (direction == RunningDirection)
                    {
                        OpenCageDoor();
                    }
                    else
                    {
                        RefreshStatus();
                    }
                }
                else
                {
                    RefreshStatus();
                }
            }
        }

        /// <summary>
        /// 清除特定楼层特定方向上的呼梯信号
        /// </summary>
        /// <param name="floorId">楼层 Id</param>
        /// <param name="direction">呼梯信号的方向</param>
        public void ClearCallCageSignals(int floorId, Direction direction)
        {
            var signals = CallCageSignals.Where(x => x.FloorId == floorId);
            if (direction != Direction.Unspecified) signals = signals.Where(x => x.Direction == direction);
            signals = signals.ToList();
            foreach (var signal in signals)
            {
                CallCageSignals.Remove(signal);
            }
        }

        #region Operations

        /// <summary>
        /// 根据当前电梯的状态，更新电梯所在的楼层，并更新电梯状态
        /// </summary>
        private void CageMoving()
        {
            _movingTimer.Stop();
            if (RunningStatus == RunningStatus.Running)
            {
                // 更新楼层信息
                switch (RunningDirection)
                {
                    case Direction.Up:
                    case Direction.Down:
                        CurrentFloor = CurrentFloor.AdjacentFloor(RunningDirection);
                        break;
                }
            }
            RefreshStatus();
        }

        /// <summary>
        /// 开启轿厢门
        /// </summary>
        private void OpenCageDoor()
        {
            _boardingTimer.Stop();
            _movingTimer.Stop();
            EraseCallFloorSignal(CurrentFloor.Id);
            EraseCallCageSignals(CurrentFloor.Id, RunningDirection);
            DoorState.Open();
            _boardingTimer.Start();
        }

        /// <summary>
        /// 关闭轿厢门
        /// </summary>
        private void CloseCageDoor()
        {
            _boardingTimer.Stop();
            DoorState.Close();
            RefreshStatus();
        }

        /// <summary>
        /// 记录呼层信号
        /// </summary>
        /// <param name="floorId"></param>
        private void RecordCallFloorSignal(int floorId) => FloorButtonStates[floorId].Press();

        /// <summary>
        /// 清除指定楼层的呼层信号
        /// </summary>
        /// <param name="floorId"></param>
        private void EraseCallFloorSignal(int floorId) => FloorButtonStates[floorId].Reset();

        /// <summary>
        /// 清除全部楼层的呼层信息
        /// </summary>
        private void EraseAllCallFloorSignals()
        {
            foreach (var state in FloorButtonStates.Values) state.Reset();
        }

        /// <summary>
        /// 记录呼梯信号
        /// </summary>
        /// <param name="signal"></param>
        private void RecordCallCageSignal(CallCageSignal signal) => CallCageSignals.Add(signal);

        /// <summary>
        /// 清除特定楼层特定方向上的呼梯信号
        /// </summary>
        /// <param name="floorId"></param>
        /// <param name="direction">呼梯信号的方向，为 <see cref="Direction.Unspecified"/> 时表示方向不限</param>
        private void EraseCallCageSignals(int floorId, Direction direction)
        {
            ClearCallCageSignals(floorId, direction);

            OnCallCageSignalsHandled(new CallCageSignalsHandledEventArgs(floorId, direction));
        }

        #endregion

        #region Conditions

        private bool IsCurrentFloor(int floorId) => floorId == CurrentFloor.Id;

        /// <summary>
        /// 判断指定楼层是否存在呼层信号
        /// </summary>
        /// <param name="floorId"></param>
        /// <returns></returns>
        private bool HasCallFloorSignal(int floorId) => FloorButtonStates[floorId].IsPressed;

        /// <summary>
        /// 判断指定楼层是否存在指定方向上的呼梯信号
        /// </summary>
        /// <param name="floorId"></param>
        /// <param name="direction">呼梯信号的方向，为 <see cref="Direction.Unspecified"/> 时表示方向不限</param>
        /// <returns></returns>
        private bool HasCallCageSignal(int floorId, Direction direction)
        {
            var signals = CallCageSignals.Where(x => x.FloorId == floorId);
            if (direction != Direction.Unspecified) signals = signals.Where(x => x.Direction == direction);
            return signals.Any();
        }

        /// <summary>
        /// 判断指定楼层的特定方向上是否有注册的呼层信号
        /// </summary>
        /// <param name="baseFloorId">指定的楼层 Id</param>
        /// <param name="relation">要判断的呼层信号与指定楼层的位置关系</param>
        /// <returns></returns>
        private bool CallFloorSignalsExist(int baseFloorId, Direction relation) => FloorButtonStates.Any(x => Compare(x.Key, baseFloorId) == relation && x.Value.IsPressed);

        /// <summary>
        /// 判断指定楼层的特定方向上是否有注册的呼梯信号
        /// </summary>
        /// <param name="baseFloorId">指定的楼层 Id</param>
        /// <param name="relation">要判断的呼梯信号所在楼层与指定楼层的位置关系</param>
        /// <param name="direction">限制要判断的呼梯信号的方向，指定为 <see cref="Direction.Unspecified"/> 表示方向不限</param>
        /// <returns></returns>
        private bool CallCageSignalsExist(int baseFloorId, Direction relation, Direction direction = Direction.Unspecified) =>
            CallCageSignals.Any(x => Compare(x.FloorId, baseFloorId) == relation && (direction == Direction.Unspecified || x.Direction == direction));

        #endregion

        #region Events

        public event EventHandler<CallCageSignalsHandledEventArgs> CallCageSignalsHandled;

        protected virtual void OnCallCageSignalsHandled(CallCageSignalsHandledEventArgs e) => CallCageSignalsHandled?.Invoke(this, e);

        #endregion

        #region Helper Methods

        /// <summary>
        /// 根据轿厢各方向上的呼层、呼梯信号分布情况，确定轿厢当前可行的运行方向
        /// </summary>
        /// <returns></returns>
        private Direction DetermineRunningDirection()
        {
            var callFloorId = FloorButtonStates.Where(x => x.Value.IsPressed).OrderBy(x => x.Value.StateChangedTime).FirstOrDefault().Key;
            if (callFloorId != default) return CompareToCurrentFloor(callFloorId);

            if (CallCageSignals.Count == 0) return Direction.Unspecified;
            var signal = CallCageSignals.OrderBy(x => x.Time).First();
            return CompareToCurrentFloor(signal.FloorId);
        }

        /// <summary>
        /// 计算特定楼层与轿厢当前停靠楼层的相对方向关系
        /// </summary>
        /// <param name="floorId">楼层 Id</param>
        /// <returns>
        ///     <see cref="Direction.Up"/> 表示给定楼层高于当前楼层，
        ///     <see cref="Direction.Down"/> 表示给定楼层低于当前楼层，
        ///     <see cref="Direction.Unspecified"/> 表示给定楼层既是当前楼层。
        /// </returns>
        private Direction CompareToCurrentFloor(int floorId) => Compare(floorId, CurrentFloor.Id);

        /// <summary>
        /// 比较两个楼层的相对位置关系
        /// </summary>
        /// <param name="floorId">要比较的目标楼层 Id</param>
        /// <param name="baseFloorId">要比较的基础楼层 Id</param>
        /// <returns>
        ///     目标楼层高于基础楼层则返回 <see cref="Direction.Up"/>，
        ///     低于基础楼层返回 <see cref="Direction.Down"/>，
        ///     两层相同返回 <see cref="Direction.Unspecified"/>
        /// </returns>
        private Direction Compare(int floorId, int baseFloorId)
        {
            switch (floorId.CompareTo(baseFloorId))
            {
                case int result when result > 0: // 呼叫楼层高于当前楼层
                    return Direction.Up;
                case int result when result < 0: // 呼叫楼层低于当前楼层
                    return Direction.Down;
                default:
                    return Direction.Unspecified;
            }
        }

        public bool Equals(Elevator other) => other == null ? false : Id == other.Id;

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(this, obj)) return true;
            if (obj is Elevator other) return Equals(other);
            return false;
        }

        public override int GetHashCode() => Id.GetHashCode();

        public int CompareTo(Elevator other) => other == null ? throw new ArgumentNullException(nameof(other)) : Id.CompareTo(other.Id);

        public override string ToString()
        {
            return $"{{ {Id} : {CurrentFloor.Id}" +
                (RunningDirection == Direction.Up ? " ↑" : string.Empty) +
                (RunningDirection == Direction.Down ? " ↓" : string.Empty) +
                (RunningStatus == RunningStatus.Running ? " (R)" : string.Empty) +
                (DoorState.IsOpen ? " (O)" : string.Empty) + " }";
        }

        #endregion
    }
}
